#include "crypt_util.h"

#include <openssl/bio.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static const unsigned char *encrypt_key =
    (unsigned char
         *)"064a25d5d757ffa88860bec502a0b3c6633cf889487ebc8882ca9eb144bae8e0";
static const unsigned char *encrypt_iv =
    (unsigned char *)"ff0d7144c8ed0691c872092293110017";

void handleErrors(void) {
    ERR_print_errors_fp(stderr);
    abort();
}

int aes_encrypt(const unsigned char *plaintext, int plaintext_len,
                unsigned char *ciphertext, const unsigned char *key_,
                const unsigned char *iv_) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;
    unsigned char key[256] = {0};
    unsigned char iv[128] = {0};
    if (plaintext_len <= 0 || plaintext == NULL || ciphertext == NULL) return 0;
    if (key_ == NULL) {
        strcpy((char *)key, (const char *)encrypt_key);
    } else {
        if (strlen((const char *)key_) < sizeof((const char *)key)) {
            strcpy((char *)key, (const char *)key_);
        } else {
            DBGE("key length is too long must < %lu\n", sizeof(key));
            return 0;
        }
    }
    if (iv_ == NULL) {
        strcpy((char *)iv, (const char *)encrypt_iv);
    } else {
        if (strlen((const char *)iv_) < sizeof((const char *)iv)) {
            strcpy((char *)iv, (const char *)iv_);
        } else {
            DBGE("iv length is too long must < %lu\n", sizeof(iv));
            return 0;
        }
    }

    // Create and initialise the context
    if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

    // Initialise the encryption operation.
    if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
        handleErrors();

    // Provide the message to be encrypted, and obtain the encrypted output.
    if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        handleErrors();
    ciphertext_len = len;

    // Finalise the encryption.
    if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
    ciphertext_len += len;

    // Clean up
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

int aes_decrypt(const unsigned char *ciphertext, int ciphertext_len,
                unsigned char *plaintext, const unsigned char *key_,
                const unsigned char *iv_) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;
    int ret;
    unsigned char key[256] = {0};
    unsigned char iv[128] = {0};
    if (ciphertext == NULL || ciphertext_len <= 0 || plaintext == NULL)
        return 0;
    if (key_ == NULL) {
        strcpy((char *)key, (const char *)encrypt_key);
    } else {
        if (strlen((const char *)key_) < sizeof((const char *)key)) {
            strcpy((char *)key, (const char *)key_);
        } else {
            DBGE("key length is too long must < %lu\n", sizeof(key));
            return 0;
        }
    }
    if (iv_ == NULL) {
        strcpy((char *)iv, (const char *)encrypt_iv);
    } else {
        if (strlen((const char *)iv_) < sizeof((const char *)iv)) {
            strcpy((char *)iv, (const char *)iv_);
        } else {
            DBGE("iv length is too long must < %lu\n", sizeof(iv));
            return 0;
        }
    }
    // Create and initialise the context
    if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

    // Initialise the decryption operation.
    if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
        handleErrors();

    // Provide the message to be decrypted, and obtain the plaintext output.
    if (1 !=
        EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
        handleErrors();
    plaintext_len = len;

    // Finalise the decryption.
    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);

    if (ret > 0) {
        // Success
        plaintext_len += len;
        EVP_CIPHER_CTX_free(ctx);
        return plaintext_len;
    } else {
        // A decryption error occurred
        EVP_CIPHER_CTX_free(ctx);
        handleErrors();
        return -1;  // Indicate failure
    }
    return plaintext_len;
}

char *base64_encode(const unsigned char *input, int length) {
    BIO *bmem, *b64;
    if (input == NULL || length <= 0) {
        DBGE("input is null or empty\n");
        return NULL;
    }
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(
        b64, BIO_FLAGS_BASE64_NO_NL);  // Do not use newlines to flush buffer
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, input, length);
    BIO_flush(b64);

    char *buff;
    long buff_len = BIO_get_mem_data(b64, &buff);
    // DBGI("buff_len = %ld\n", buff_len);

    char *output = (char *)malloc(buff_len + 1);
    memcpy(output, buff, buff_len);
    output[buff_len] = 0;

    BIO_free_all(b64);

    return output;
}

unsigned char *base64_decode(const char *input, int *length) {
    BIO *b64, *bmem;
    *length = 0;
    int input_length = strlen(input);
    unsigned char *buffer = NULL;

    if (input == NULL) return NULL;
    buffer =
        (unsigned char *)malloc(input_length);  // 解码后长度肯定小于输入长度
    if (buffer == NULL) return NULL;
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    bmem = BIO_new_mem_buf(
        input, -1);  // -1 indicates that the input length is strlen(input)
    bmem = BIO_push(b64, bmem);

    *length = BIO_read(bmem, buffer, input_length);

    BIO_free_all(bmem);

    buffer = (unsigned char *)realloc(buffer,
                                      *length + 1);  // 重新分配以匹配实际长度
    buffer[*length] = '\0';

    return buffer;
}

int encrypt_base64(const void *plaintext_, int plaintext_len, char *ciphertext,
                   int ciphertext_len) {
    unsigned char ciphertext_[1024] = {0};
    char *base64_ = NULL;
    int len = 0;
    const unsigned char *plaintext = (const unsigned char *)plaintext_;
    if (plaintext == NULL || plaintext_len <= 0) {
        DBGE("plaintext is null or empty\n");
        return 0;
    }
    if (ciphertext == NULL || ciphertext_len <= 0) {
        DBGE("ciphertext is null or empty\n");
        return 0;
    }
    if (plaintext_len > 256) {
        DBGE("plaintext length is too long must < 256\n");
        return 0;
    }
    len = aes_encrypt(plaintext, plaintext_len, ciphertext_, NULL, NULL);
    if (len <= 0) {
        DBGE("aes encrypt error\n");
        return 0;
    }
    if (len >= ciphertext_len) {
        DBGE("ciphertext length is too short must > %d\n", len);
        return 0;
    }
    base64_ = base64_encode(ciphertext_, len);
    if (base64_ == NULL) {
        DBGE("base64 encode error\n");
        return 0;
    }
    len = strlen(base64_);
    if (len >= ciphertext_len) {
        DBGE("ciphertext length is too short must > %d\n", len);
        return 0;
    }
    memcpy(ciphertext, base64_, len);
    ciphertext[len] = 0x0;
    free(base64_);
    base64_ = NULL;
    // DBGI("base64 = %s\n", ciphertext);
    return len;
}

int decrypt_base64(const char *base64_, int base64_len, void *plaintext_,
                   int plaintext_len) {
    unsigned char *plaintext = (unsigned char *)plaintext_;
    unsigned char *base64_dec = NULL;
    int len = 0, ciphertext_len = 0;
    if (plaintext == NULL || plaintext_len <= 0) return 0;
    if (base64_ == NULL || base64_len <= 0) return 0;
    if (base64_len > 256) {
        DBGE("ciphertext length is too long must < 256\n");
        return 0;
    }
    base64_dec = base64_decode(base64_, &len);
    if (base64_dec == NULL || len <= 0) {
        if (base64_dec) {
            free(base64_dec);
            base64_dec = NULL;
        }
        return 0;
    }
    if (plaintext_len < len) {
        DBGE("plaintext length is too short must > %d\n", len);
        free(base64_dec);
        base64_dec = NULL;
        return 0;
    }
    ciphertext_len = aes_decrypt((const unsigned char *)base64_dec, len,
                                 plaintext, NULL, NULL);
    if (ciphertext_len <= 0) {
        free(base64_dec);
        base64_dec = NULL;
        DBGE("aes decrypt error\n");
        return 0;
    }
    free(base64_dec);
    base64_dec = NULL;
    plaintext[ciphertext_len] = 0x0;
    return ciphertext_len;
}